# -*- coding: utf-8 -*-

import ast

from ir.ir_statement import *
from ir.ir_node import IVarParam, IExpThis
from ir.ir_value import *
from ir.ir_ds import *
from ir.ir_ports import *
from ir.ir_helper import Helper, ClassHelper
from ir.ir_node import ITypeNode, IVarTemp, IVarPort, IVarReg, IVarNode

from .convert_type import convert_ast_to_type
from .local_vars import start_local_var, end_local_var


def convert_num(ast_num: ast.Num) -> IExpValue:
    # Extract the number.
    number = ast_num.n
    # Based on the type of number, return different value.
    if isinstance(number, int):
        number = IValueInt(number)
    elif isinstance(number, float):
        number = IValueFloat(number)
    elif isinstance(number, complex):
        number = IValueComplex(number)
    return IExpValue(number)


def convert_str(ast_str: ast.Str) -> IExpValue:
    # Directly extract the value from string.
    return IExpValue(IValueStr(ast_str.s))


def convert_unary(ast_unary: ast.UnaryOp, helper: Helper, this: ITypeNode) -> IExpUnary:
    # Convert the value.
    operand = convert_exp(ast_unary.operand, helper, this)
    # Check the operation.
    if isinstance(ast_unary.op, ast.USub):
        uop = IExpUnary.USUB
        # Check the special type of value.
        if isinstance(operand, IValue):
            if isinstance(operand.type, ITypeInt) or \
                    isinstance(operand.type, ITypeFloat):
                operand.value *= -1
                return operand
        # For expression value process.
        if isinstance(operand, IExpValue):
            if isinstance(operand.value, IValueInt) or \
                    isinstance(operand.value, IValueFloat):
                operand.value.value *= -1
                return operand
    elif isinstance(ast_unary.op, ast.Not):
        uop = IExpUnary.NOT
    else:
        raise Exception('Unimplemented unary operation: ' + str(ast_unary.op))
    # Give back the operation.
    return IExpUnary(operand, uop, convert_ast_to_type(operand, helper))


def convert_nameconst(ast_nc: ast.NameConstant) -> IExpValue:
    # Check the value of the AST.
    if ast_nc.value is None:
        return None
    if ast_nc.value is False or ast_nc.value is True:
        return IExpValue(IValueBool(ast_nc.value))
    raise Exception('Unimplemented NameConstant convert for ' + str(ast_nc.__dict__))


def convert_dict(ast_dict: ast.Dict, helper: Helper, this: ITypeNode) -> IExpValue:
    # Build the dictionary ir.
    ir_dict = IValueDict()
    # Convert keys and values.
    if len(ast_dict.keys) > 0:
        # Loop for the key and value pair.
        for ast_key, ast_value in zip(ast_dict.keys, ast_dict.values):
            if not isinstance(ast_key, ast.Str):
                # Only support dictionary with string keys.
                raise Exception('Only string constant could be used as a key.')
            # Extract the key and value.
            ir_value = convert_exp(ast_value, helper, this)
            # Set the value.
            ir_dict.value[ast_key.s] = ir_value
    return IExpValue(ir_dict)


def convert_list(ast_list: ast.List, helper: Helper, this: ITypeNode) -> IExpValue:
    # Convert the element in the list.
    items = []
    for ast_element in ast_list.elts:
        ir_item = convert_exp(ast_element, helper, this)
        # Append element data.
        items.append(ir_item)
    # Construct the list.
    if len(items) > 0:
        item_type = items[0].type
    else:
        item_type = None
    # Still no name for the list.
    ir_list = IValueList(item_type)
    ir_list.value = items
    # Give back the list item.
    return IExpValue(ir_list)


def convert_name(ast_name: ast.Name, helper: Helper, this: ITypeNode):
    variable_name = ast_name.id
    # Check the id of the name.
    if variable_name == 'self':
        # Check this pointer.
        if this is None or not isinstance(this, ITypeNode):
            raise Exception('Invalid this reference detected.')
        # Well it is this.
        return IExpThis(this)
    # Check for local variable.
    if variable_name in helper.local:
        # Fetch the variable from local variable map.
        return helper.local[variable_name]
    # Check self structural data.
    if variable_name == 'params':
        # This is a variable type data.
        return IVarParam
    if variable_name == 'len':
        return IOpLen
    if variable_name == '_':
        return IExpVoid
    # Check whether the name appears in types.
    if variable_name in helper.types:
        # It is a type.
        return helper.types[variable_name]
    # Try to convert it as a type.
    try:
        return convert_ast_to_type(ast_name, helper)
    except:
        pass
    raise Exception('Unimplemented Name type variable: {}.'.format(variable_name))


def fetch_member_from_node_type(parent, type_info: ITypeNode, helper: Helper, attr: str):
    # We need to add an 's' to the end of the attribute name for checking.
    type_key = attr + 's'
    if not hasattr(type_info, type_key):
        # It might be the function, find in the node type helper.
        node_helper = helper.chelper['Node']
        if not attr in node_helper.func:
            raise Exception('Failed to find {} in node type {}.'.format(attr, parent))
        # Now it is a defined as a function type.
        return IExpFunc(attr, parent, node_helper.func[attr])
    # Extract the data from the node.
    return IExpMember(parent, attr, None, getattr(type_info, type_key))


def fetch_member_from_type(parent, parent_type, helper: Helper, attr: str):
    # Fetching node type.
    if isinstance(parent_type, ITypeNode):
        return fetch_member_from_node_type(parent, parent_type, helper, attr)
    # Check the parent type.
    if hasattr(parent_type, 'name'):
        type_helper = helper.chelper[parent_type.name]
    elif isinstance(parent_type, ITypePortBase):
        type_helper = helper.chelper[parent_type.type]
    else:
        raise Exception('Unknown parent type {}.'.format(parent_type))
    # Check whether the attr is function.
    if attr in type_helper.func:
        # Fetch the function definition.
        func_prototype = type_helper.func[attr]
        # Check the return type and parameter type.
        ret_type, _ = func_prototype
        if isinstance(ret_type, IGeneric):
            # Wei, need to check parent type generic.
            if not hasattr(parent_type, 'generic') or ret_type.mark not in parent_type.generic:
                raise Exception('Failed to extract generic type {} from {}'.format(ret_type.mark, parent))
            # Copy the function definition, and replace the return type.
            extract_func_prototype = func_prototype.copy()
            extract_func_prototype[0] = parent_type.generic[ret_type.mark]
            # Use extract prototype as the function prototype.
            func_prototype = extract_func_prototype
        return IExpFunc(attr, parent, func_prototype)
    # So now, it should be a member.
    if attr in type_helper.member:
        return IExpMember(parent, attr, type_helper.member[attr], None)
    raise Exception('Unimplemented member fetching {} from {}.'.format(attr, parent))


def fetch_member_from_var(parent, helper: Helper, attr: str):
    return fetch_member_from_type(parent, parent.type, helper, attr)


def convert_attr(ast_attr: ast.Attribute, helper: Helper, this: ITypeNode):
    # Convert the parent value.
    parent = convert_exp(ast_attr.value, helper, this)
    # Get attribute string.
    attr = ast_attr.attr
    # Based on the parent, parse the attribute data.
    if parent == IVarParam:
        # Try to find the parameter in this.
        if attr in this.params:
            return this.params[attr]
        else:
            # Construct a new parameter, it is a param definition.
            return IVarParam(attr, None)
    if isinstance(parent, IVarPort) or isinstance(parent, IVarReg) or \
            isinstance(parent, IExpSubscript):
        # The parent is a port or register, check its attribute.
        return fetch_member_from_var(parent, helper, attr)
    if isinstance(parent, IExpThis):
        # Fetch the member from node type.
        return fetch_member_from_node_type(parent, parent.node_type, helper, attr)
    if isinstance(parent, IExpMember) and \
            (not isinstance(parent.type, ITypeLanePort) and
             not isinstance(parent.type, ITypeChannelPort)):
        # Check the parent type first.
        if parent.type is None:
            # Based on the value of its previous data,
            if parent.value is not None:
                # Check the value type.
                if isinstance(parent.value, dict):
                    # Consider whether it is from this.
                    dict_parent = parent.parent
                    # Try to find the the data.
                    if isinstance(dict_parent, IExpThis):
                        if attr in parent.value:
                            # Extract the member data from the dictionary.
                            return parent.value[attr]
                    else:
                        # A Non-this parent is detected, in this case, still
                        # fetch the type, but not treat as a variable.
                        if attr not in parent.value:
                            raise Exception('Failed to find {} in node {}.'.format(attr, dict_parent))
                        # Get the variable.
                        target_variable = parent.value[attr]
                        return IExpMember(dict_parent, target_variable, target_variable.type)
                else:
                    raise Exception('Unknown parent value {} detected.'.format(parent.value))
        else:
            # Parent is not None, check the parent type.
            return fetch_member_from_var(parent, helper, attr)
        return IExpMember(parent, attr, None, None)
    if isinstance(parent.type, ITypePortIn) or \
            isinstance(parent.type, ITypePortInDirect) or \
            isinstance(parent.type, ITypePortOut) or \
            isinstance(parent.type, ITypeLanePort) or \
            isinstance(parent.type, ITypeChannelPort) or \
            isinstance(parent.type, ITypeNode) or \
            isinstance(parent, IVarTemp):
        # Fetch the type in helper.
        return fetch_member_from_var(parent, helper, attr)
    if isinstance(parent, ITypeEnum):
        # It is just an enumeration value, get the value from the type.
        return IExpValue(parent.get(attr))
    if isinstance(parent, ITypeChannel):
        return fetch_member_from_type(parent, parent, helper, attr)
    raise Exception('Unknown parent type {} detected.'.format(type(parent)))


def convert_len(params: list, helper: Helper, this: ITypeNode):
    # Only 1 parameters is expected.
    if len(params) != 1:
        raise Exception('len() should have only 1 parameter.')
    # Convert the variable.
    ir_var = convert_exp(params[0], helper, this)
    # Construct the len operation.
    return IOpLen(ir_var)


def convert_type_force_interpret(func_ptr, params: list, helper: Helper, this: ITypeNode):
    # Check whether it is a construct calling.
    if len(params) == 0:
        # This is a constructor of the type.
        return IOpConstruct(func_ptr)
    # Only 1 parameters is expected.
    if len(params) != 1:
        raise Exception('Format convert should have 1 parameter.')
    # Convert the variable.
    ir_var = convert_exp(params[0], helper, this)
    # Construct the convert operation.
    return IOpTypeConvert(ir_var, func_ptr)


def convert_call(ast_call: ast.Call, helper: Helper, this: ITypeNode):
    # Extract the function member from the parent.
    func_ptr = convert_exp(ast_call.func, helper, this)
    # Check the function definition.
    if not isinstance(func_ptr, IExpFunc):
        # It might be length operation.
        if func_ptr == IOpLen:
            return convert_len(ast_call.args, helper, this)
        if isinstance(func_ptr, IType):
            return convert_type_force_interpret(func_ptr, ast_call.args, helper, this)
        raise Exception('Failed to find function in call {} of type {}.'.format(func_ptr, type(func_ptr)))
    # Extract the function parameter.
    func_param = func_ptr.params
    # Check whether it accept any parameters.
    if len(func_param) == 1 and func_param[0] == '*':
        # It accept any parameter, directly build the param list.
        param_list = []
        for param in ast_call.args:
            # Parse the parameter.
            param_list.append(convert_exp(param, helper, this))
    else:
        # Parse the calling parameters.
        call_param = {}
        for param_format, param in zip(func_param, ast_call.args):
            # Parse the parameter.
            param_value = convert_exp(param, helper, this)
            # Extract the parameter name.
            _, param_name, _ = param_format
            # Save the parameter data.
            call_param[param_name] = param_value
        # Loop for all the keyword parameter.
        for ast_keyword in ast_call.keywords:
            # Save parameter name.
            param_name = ast_keyword.arg
            # Parse the parameter.
            param_value = convert_exp(ast_keyword.value, helper, this)
            # Save the parameter data.
            call_param[param_name] = param_value
        # Based on the parameter name, generate the parameter data.
        param_list = []
        for _, param_name, param_default in func_param:
            if param_name in call_param:
                # Add the value in dictionary.
                param_list.append(call_param[param_name])
            else:
                # Check whether the default value is empty.
                if param_default is None:
                    # Which means that this variable cannot be ignored.
                    raise Exception('Parameter {} of cannot be ignored.'.format(param_name))
                # Add the default parameter.
                param_list.append(param_default)
    # Construct a function call.
    return IOpCall(func_ptr, param_list)


def convert_tuple(ast_tuple: ast.Tuple, helper: Helper, this: ITypeNode):
    # Convert all the elements into a list.
    tuple_list = []
    # Loop and detect the data.
    for ast_element in ast_tuple.elts:
        tuple_list.append(convert_exp(ast_element, helper, this))
    # Give the tuple list back.
    return IExpSet(tuple_list)


def convert_compare_chain(ir_left, ast_rights: list, ast_ops: list, helper: Helper, this: ITypeNode):
    # Extract the right and op.
    ast_right = ast_rights[0]
    ast_op = ast_ops[0]
    # Check the compare length.
    if len(ast_ops) > 1:
        # Create the next left, op, and right.
        next_rights = ast_rights[1:]
        next_ops = ast_ops[1:]
        # There are still next staff need to be processed.
        has_next = True
    else:
        has_next = False
    # Convert the right.
    ir_right = convert_exp(ast_right, helper, this)
    next_left = ir_right
    # Extract the first right item and compare item.
    if isinstance(ast_op, ast.In):
        return IOpContain(ir_right, ir_left)
    elif isinstance(ast_op, ast.GtE) or isinstance(ast_op, ast.LtE):
        # Construct the equal.
        ir_eq = IExpBinary(ir_left, IExpBinary.EQUAL, ir_right, ITypeBool)
        ir_diff = IExpBinary(ir_left,
                             IExpBinary.LESSER if isinstance(ast_op,
                                                             ast.LtE) else IExpBinary.GREATER,
                             ir_right, ITypeBool)
        # Construct the current exp the and result.
        current_exp = IExpBinary(ir_eq, IExpBinary.OR, ir_diff, ITypeBool)
    else:
        if isinstance(ast_op, ast.Eq):
            bin_op = IExpBinary.EQUAL
        elif isinstance(ast_op, ast.NotEq):
            bin_op = IExpBinary.NOTEQUAL
        elif isinstance(ast_op, ast.Gt):
            bin_op = IExpBinary.GREATER
        elif isinstance(ast_op, ast.Lt):
            bin_op = IExpBinary.LESSER
        else:
            raise Exception('Unimplemented compare operator {}.'.format(ast_op))
        current_exp = IExpBinary(ir_left, bin_op, ir_right, ITypeBool)
    # Now we need to do the following compare, if we need.
    if has_next:
        # We need to parse the following item.
        ir_next = convert_compare_chain(next_left, next_rights, next_ops, helper, this)
        # Using AND to connect the next condition and current condition.
        return IExpBinary(current_exp, IExpBinary.AND, ir_next, ITypeBool)
    # Or else, the current exp is the result.
    return current_exp


def convert_compare(ast_compare: ast.Compare, helper: Helper, this: ITypeNode):
    # Parse the first left operand, then start to parse the compare chain.
    return convert_compare_chain(convert_exp(ast_compare.left, helper, this),
                                 ast_compare.comparators, ast_compare.ops,
                                 helper, this)


def generate_binop(op, ir_left, ir_right, helper: Helper):
    # Based on the op to compare the data.
    if isinstance(op, ast.Add):
        bin_op = IExpBinary.ADD
    elif isinstance(op, ast.Sub):
        bin_op = IExpBinary.SUB
    elif isinstance(op, ast.Mult):
        bin_op = IExpBinary.MULT
    elif isinstance(op, ast.Div):
        bin_op = IExpBinary.DIV
    elif isinstance(op, ast.Mod):
        bin_op = IExpBinary.MOD
    elif isinstance(op, ast.LShift):
        bin_op = IExpBinary.BLSHIFT
    elif isinstance(op, ast.RShift):
        bin_op = IExpBinary.BRSHIFT
    elif isinstance(op, ast.BitOr):
        bin_op = IExpBinary.BOR
    elif isinstance(op, ast.BitAnd):
        bin_op = IExpBinary.BAND
    elif isinstance(op, ast.BitXor):
        bin_op = IExpBinary.BXOR
    else:
        raise Exception('Unimplemented binary op: ' + str(op))
    return IExpBinary(ir_left, bin_op, ir_right, convert_ast_to_type(ir_left, helper))


def convert_binop(ast_binop: ast.BinOp, helper: Helper, this: ITypeNode):
    # Get left and right operand and op.
    return generate_binop(ast_binop.op,
                          convert_exp(ast_binop.left, helper, this),
                          convert_exp(ast_binop.right, helper, this),
                          helper)


def convert_boolop(ast_boolop: ast.BoolOp, helper: Helper, this: ITypeNode):
    # Based on the boolean operation, check the result.
    if isinstance(ast_boolop.op, ast.Or) or isinstance(ast_boolop.op, ast.And):
        # Need to convert two values.
        if len(ast_boolop.values) < 2:
            raise Exception('Incorrect bool operation operand number detected.')
        # Get left and right.
        ir_left = convert_exp(ast_boolop.values[0], helper, this)
        ir_right = convert_exp(ast_boolop.values[1], helper, this)
        # Give back the binary operation description.
        return IExpBinary(ir_left,
                          IExpBinary.OR if isinstance(ast_boolop.op, ast.Or) else IExpBinary.AND,
                          ir_right,
                          ITypeBool)
    raise Exception('Unimplemented bool operation {}.'.format(ast_boolop.op))


def convert_for_header_of_list(ir_var, ir_type: ITypeList, ast_id, ast_iter_var,
                               iter_init, iter_var_map):
    # Extract the managed type.
    tmp_var_type = ir_type.managed_type
    # Build the range.
    ir_range = IOpRange(0, IOpLen(ir_var), 1)
    # Create the iteration variable.
    ir_iter_var = IVarTemp('temp_{}'.format(ast_id), ITypeInt)
    # Check the iteration variable ast.
    if not isinstance(ast_iter_var, ast.Name):
        raise Exception('Unknown for-loop iteration var detected.')
    var_name = ast_iter_var.id
    if var_name != '_':
        # Create the variable and add it into temp
        item_var = IVarTemp(var_name, tmp_var_type)
        iter_var_map[var_name] = item_var
        iter_init.append(IOpAssign(item_var, IExpSubscript(ir_var, ir_iter_var, tmp_var_type)))
    return ir_iter_var, ir_range


def convert_for_header_of_zip(ast_id, ast_iter_var: ast.Tuple, iter_params, helper: Helper, this: ITypeNode,
                              iter_init, iter_var_map):
    # It should provided multiple list type variable here.
    ir_zip_lists = []
    for ast_list in iter_params:
        ir_zip_lists.append(convert_exp(ast_list, helper, this))
    # We would choose the first one as the length.
    ir_range = IOpRange(0, IOpLen(ir_zip_lists[0]), 1)
    # Create the iteration variable.
    ir_iter_var = IVarTemp('temp_{}'.format(ast_id), ITypeInt)
    # Create the variable and add it into temp
    for ast_temp_var, ir_list in zip(ast_iter_var.elts, ir_zip_lists):
        # Check the variable type.
        if not isinstance(ast_temp_var, ast.Name):
            raise Exception('Only simple temp variable is allowed, but not {}.'.format(ast_temp_var))
        # Extract the variable name and type.
        var_name = ast_temp_var.id
        ir_list_managed_type = ir_list.type.managed_type
        # Create temp variable.
        item_var = IVarTemp(var_name, ir_list_managed_type)
        iter_var_map[var_name] = item_var
        iter_init.append(IOpAssign(item_var, IExpSubscript(ir_list, ir_iter_var, ir_list_managed_type)))
    return ir_iter_var, ir_range


def convert_for_header(ast_iter_var, ast_iter, helper: Helper, this: ITypeNode):
    iter_var_map = {}
    iter_init = []
    # Consider the type of iter ast.
    if isinstance(ast_iter, ast.Call):
        # It might be range or zip.
        if not isinstance(ast_iter.func, ast.Name):
            raise Exception('Unimplemented for loop iterator function: ' + str(ast_iter.func))
        iter_func_name = ast_iter.func.id
        iter_params = ast_iter.args
        if iter_func_name == 'range':
            # Parse the parameters.
            range_start = 0
            range_step = 1
            if len(iter_params) == 1:
                # Which means it is: range(0, xx, 1)
                range_end = convert_exp(iter_params[0], helper, this)
            elif len(iter_params) == 2:
                # Which means it is: range(xx, xx, 1)
                range_start = convert_exp(iter_params[0], helper, this)
                range_end = convert_exp(iter_params[1], helper, this)
            elif len(iter_params) == 3:
                # Which means it is: range(xx, xx, xx)
                range_start = convert_exp(iter_params[0], helper, this)
                range_end = convert_exp(iter_params[1], helper, this)
                range_step = convert_exp(iter_params[2], helper, this)
            else:
                raise Exception('Unknown range parameter detected.')
            # Generate the iterator.
            ir_range = IOpRange(range_start, range_end, range_step)
            # Check the iteration variable.
            if not isinstance(ast_iter_var, ast.Name):
                raise Exception('Unknown for-loop iteration var detected.')
            var_name = ast_iter_var.id
            if var_name == '_':
                # Create a temp var for this.
                iter_var = IVarTemp('temp_{}'.format(id(ast_iter)), ITypeInt)
            else:
                # Create the variable and add it into temp
                iter_var = IVarTemp(var_name, ITypeInt)
                iter_var_map[var_name] = iter_var
            return iter_var, ir_range, [], iter_var_map
        if iter_func_name == 'zip':
            if not isinstance(ast_iter_var, ast.Tuple):
                raise Exception('To loop in zip lists, variable must be extracted in for loop.')
            # Extract the value.
            iter_var, ir_range = \
                convert_for_header_of_zip(id(ast_iter), ast_iter_var, iter_params, helper, this,
                                          iter_init, iter_var_map)
            return iter_var, ir_range, iter_init, iter_var_map
        else:
            raise Exception('Unimplemented iteration function {}.'.format(iter_func_name))
    elif isinstance(ast_iter, ast.Attribute) or isinstance(ast_iter, ast.Subscript):
        # Find the variable.
        ir_var = convert_exp(ast_iter, helper, this)
        # Convert the list iteration.
        if not isinstance(ir_var.type, ITypeList):
            raise Exception('Port {} of type {} is not iterable.'.format(ir_var, ir_var.type))
        # Loop for the list.
        iter_var, ir_range = \
            convert_for_header_of_list(ir_var, ir_var.type, id(ast_iter), ast_iter_var,
                                       iter_init, iter_var_map)
        return iter_var, ir_range, iter_init, iter_var_map
    raise Exception('Unimplemented iteration type {}.'.format(ast_iter))


def fetch_port_type_from_call(ass_exp: IOpCall, helper: Helper):
    # Check whether the function is generate port from type.
    func_def = ass_exp.funcdef
    # Extract the paraemeters.
    func_param = ass_exp.params
    if isinstance(func_def.parent, IExpThis):
        # THe function call should be this.xxx, which is the function
        # we wanted.
        if func_def.func_name == 'port_in':
            ir_port_type = ITypePortIn(managed_type=func_param[1],
                                       max_size=func_param[0])

        elif func_def.func_name == 'port_in_direct':
            ir_port_type = ITypePortInDirect(managed_type=func_param[0])
        elif func_def.func_name == 'port_out':
            ir_port_type = ITypePortOut(managed_type=func_param[0])
        elif func_def.func_name == 'port_channel_sender' or \
                func_def.func_name == 'port_channel_receiver':
            # Fetch the channel type from parameter.
            if not isinstance(func_param[0], ITypeChannel):
                raise Exception(
                    'Invalid channel type to generate channel port.')
            # Fetch the channel name.
            channel_name = func_param[0].name
            port_type = func_def.func_name[len('port_channel_'):]
            if port_type == 'receiver':
                port_type = channel_name + 'Receiver'
            else:
                port_type = channel_name + 'Sender'
            if not port_type in helper.types:
                raise Exception(
                    'Unable to find channel port {}'.format(port_type))
            ir_port_type = helper.types[port_type]
        elif func_def.func_name == 'port_bypass':
            # It is a reference of the other port.
            ir_port_type = func_param[0].type
        else:
            raise Exception('Unimplemented port generation function {}.'.format(func_def.func_name))
        return ir_port_type
    raise Exception('Unknown function {} to generate port.'.format(func_def))


def fetch_port_type(ir_exp, helper: Helper):
    # Based on the exp type, do different finding.
    if isinstance(ir_exp, IOpCall):
        return fetch_port_type_from_call(ir_exp, helper)
    elif isinstance(ir_exp, IExpMember):
        # Extract a port type from an expression.
        return ir_exp.type
    else:
        raise Exception('Unknown port type expression {}.'.format(type(ir_exp)))


def convert_listcomp(ast_listcomp: ast.ListComp, helper: Helper, this: ITypeNode):
    # Build the iterator information first.
    if len(ast_listcomp.generators) > 1:
        raise Exception('Unimplemented more than 1 generators in ListComp.')
    ast_generator = ast_listcomp.generators[0]
    # Get the variable, range, initial code and local map.
    iter_var, iter_range, iter_init, local_map = \
        convert_for_header(ast_generator.target, ast_generator.iter,
                           helper, this)
    # Start convert the body which is the build exp.
    ticket = str(id(ast_listcomp))
    start_local_var(ticket, local_map, helper)
    # Convert the constructor.
    build_exp = convert_exp(ast_listcomp.elt, helper, this)
    end_local_var(ticket, helper)
    # Based on the expression, find its type.
    managed_type = convert_ast_to_type(build_exp, helper)
    # Build the data.
    return IValueListComp(managed_type, build_exp, iter_range, iter_var, iter_init)


def convert_subscript(ast_subscript: ast.Subscript, helper: Helper, this: ITypeNode):
    # Convert the variable itself.
    ir_base = convert_exp(ast_subscript.value, helper, this)
    # The target expression should be a variable.
    if not isinstance(ir_base, IVar) and not isinstance(ir_base, IExpMember):
        raise Exception('Subscript could only used on variable, but not {}.'.format(type(ir_base)))
    # Check the type of the variable.
    variable_type = ir_base.type
    if not isinstance(variable_type, ITypeList):
        raise Exception('Subscript could only be used on list type variable, but not {}.'.format(variable_type))
    # Convert the index.
    ir_index = convert_exp(ast_subscript.slice.value, helper, this)
    # Generate the subscript.
    return IExpSubscript(ir_base, ir_index, variable_type.managed_type)


def convert_ifexp(ast_ifexp: ast.IfExp, helper: Helper, this: ITypeNode):
    # Convert the test condition.
    ir_condition = convert_exp(ast_ifexp.test, helper, this)
    ir_body = convert_exp(ast_ifexp.body, helper, this)
    ir_elbody = convert_exp(ast_ifexp.orelse, helper, this)
    # Give back the if exp.
    return IOpIfExp(ir_condition, ir_body, ir_elbody)


def convert_exp(ast_exp, helper: Helper, this: ITypeNode) :
    if isinstance(ast_exp, ast.Num):
        return convert_num(ast_exp)
    if isinstance(ast_exp, ast.Str):
        return convert_str(ast_exp)
    if isinstance(ast_exp, ast.Name):
        return convert_name(ast_exp, helper, this)
    if isinstance(ast_exp, ast.UnaryOp):
        return convert_unary(ast_exp, helper, this)
    if isinstance(ast_exp, ast.BinOp):
        return convert_binop(ast_exp, helper, this)
    if isinstance(ast_exp, ast.BoolOp):
        return convert_boolop(ast_exp, helper, this)
    if isinstance(ast_exp, ast.Tuple):
        return convert_tuple(ast_exp, helper, this)
    if isinstance(ast_exp, ast.List):
        return convert_list(ast_exp, helper, this)
    if isinstance(ast_exp, ast.ListComp):
        return convert_listcomp(ast_exp, helper, this)
    if isinstance(ast_exp, ast.NameConstant):
        return convert_nameconst(ast_exp)
    if isinstance(ast_exp, ast.Dict):
        return convert_dict(ast_exp, helper, this)
    if isinstance(ast_exp, ast.Attribute):
        return convert_attr(ast_exp, helper, this)
    if isinstance(ast_exp, ast.Call):
        return convert_call(ast_exp, helper, this)
    if isinstance(ast_exp, ast.Compare):
        return convert_compare(ast_exp, helper, this)
    if isinstance(ast_exp, ast.Subscript):
        return convert_subscript(ast_exp, helper, this)
    if isinstance(ast_exp, ast.IfExp):
        return convert_ifexp(ast_exp, helper, this)
    raise Exception('Unsupported expression type {0} for exp {1}'.format(type(ast_exp),ast_exp.__dict__))